/**
 * 8051虚拟机具体实现
 *
 * 创建时间：2022年5月21日
 * 作者：李述铜
 * 联系邮箱: 527676163@qq.com
 */
#include <string.h>
#include "simu8051.h"

static simu8051_t simu;

static uint8_t read_bit(uint8_t addr) {
	uint8_t bit_idx = addr % 8;

	if (addr < MEM_BIT_SFR_START) {
		uint8_t byte_idx = addr / 8 + MEM_BIT_IRAM_START;
		return simu.mem.iram[byte_idx] & (1 << bit_idx) ? 1 : 0;
	}
	else {
		// 0,1,2,3,4
		// 0, 8, 16, 24
		uint8_t byte_indx = (addr - MEM_BIT_SFR_START) / 8 * 8;
		return simu.mem.sfr[byte_indx] & (1 << bit_idx) ? 1 : 0;
	}
}

static void write_bit(uint8_t addr, int bit) {
	uint8_t bit_idx = addr % 8;

	bit &= 0x1;
	if (addr < MEM_BIT_SFR_START) {
		uint8_t byte_idx = addr / 8 + MEM_BIT_IRAM_START;
		simu.mem.iram[byte_idx] &= ~(1 << bit_idx);
		simu.mem.iram[byte_idx] |= bit ? (1 << bit_idx) : 0;
	}
	else {
		uint8_t byte_idx = (addr - MEM_BIT_SFR_START) / 8 * 8;
		simu.mem.sfr[byte_idx] &= ~(1 << bit_idx);
		simu.mem.sfr[byte_idx] |= bit ? (1 << bit_idx) : 0;
	}
}

static uint8_t read_sfr(uint8_t addr) {
	switch (addr) {
	case SFR_SBUF:
		return simu8051_uart_read();
	default:
		addr -= MEM_BIT_SFR_START;
		return (addr < MEM_SFR_SIZE) ? simu.mem.sfr[addr] : 0;
	}
}

static void write_sfr(uint8_t addr, uint8_t data) {
	switch (addr) {
	case SFR_SBUF:
		simu8051_uart_write(data);
		write_bit(SFR_SCON_TI, 1);
		break;
	default:
		addr -= MEM_BIT_SFR_START;
		if (addr < MEM_SFR_SIZE) {
			simu.mem.sfr[addr] = data;
		}
		break;
	}
}

static uint8_t read_iram(uint8_t addr) {
	uint8_t data;

	if (addr >= MEM_IRAM_SIZE) {
		data = read_sfr(addr);
	}
	else {
		data = simu.mem.iram[addr];
	}

	return data;
}

static void write_iram(uint8_t addr, uint8_t data) {
	if (addr >= MEM_IRAM_SIZE) {
		write_sfr(addr, data);
	}
	else {
		simu.mem.iram[addr] = data;
	}
}

static uint8_t read_xram(uint16_t addr) {
	return simu.mem.xram[addr];
}

static void write_xram(uint16_t addr, uint8_t data) {
	simu.mem.xram[addr] = data;
}

static uint8_t read_code(uint16_t addr) {
	return simu.mem.code[addr];
}

static void write_code(uint16_t addr, uint8_t data) {
	simu.mem.code[addr] = data;
}


uint8_t simu8051_read(mem_type_t type, uint16_t addr) {
	switch (type) {
	case MEM_TYPE_IRAM:
		return read_iram((uint8_t)addr);
	case MEM_TYPE_XRAM:
		return read_xram(addr);
	case MEM_TYPE_CODE:
		return read_code(addr);
	case MEM_TYPE_SFR:
		return read_sfr((uint8_t)addr);
	case MEM_TYPE_BIT:
		return read_bit((uint8_t)addr);
	default:
		return 0;
	}
}

void simu8051_write(mem_type_t type, uint16_t addr, uint8_t data) {
	switch (type) {
	case MEM_TYPE_IRAM:
		write_iram((uint8_t)addr, data);
		break;
	case MEM_TYPE_XRAM:
		write_xram(addr, data);
		break;
	case MEM_TYPE_CODE:
		write_code(addr, data);
		break;
	case MEM_TYPE_SFR:
		write_sfr((uint8_t)addr, data);
		break;
	case MEM_TYPE_BIT:
		write_bit((uint8_t)addr, data);
		break;
	default:
		break;
	}
}

// mov 0x20, A
static uint8_t get_op(instr_t* instr, int op_num) {
	const instr_info_t* info = instr->info;

	if (op_num == 0) {
		switch (info->op0_mode) {
		case OTYPE_IMM8:
		case OTYPE_DIRECT:				
		case OTYPE_BIT:
			return instr->op0;
		default:
			return 0;
		}
	}
	else {
		return (info->bytes > 2) ? instr->op1 : instr->op0;
	}

}

static int get_rs(void) {
	return (read_sfr(SFR_PSW) >> 3) & 0x3;
}

static uint16_t read_op(instr_t* instr, int op_num, mem_type_t mem_type) {
	const instr_info_t* info = instr->info;
	op_type_t op_mode = op_num ? info->op1_mode : info->op0_mode;
	uint8_t op = get_op(instr, op_num);
	switch (op_mode) {
		case OTYPE_Rn: {
			int reg = instr->opcode & 0x7;		// 0,1,2, 7
			return (simu.mem.iram + get_rs() * 8)[reg]; // simu.mem.iram[reg];			
		}
		case OTYPE_IMM8: {
			return op;
		}
		case OTYPE_ACC: {
			return read_sfr(SFR_ACC);
		}
		case OTYPE_DIRECT: {
			return read_iram(op);
		}
		case OTYPE_DPTR: {
			return (read_sfr(SFR_DPH) << 8) + read_sfr(SFR_DPL);
		}
		case OTYPE_IMM16: {
			return (instr->op0 << 8) | instr->op1;
		}
		case OTYPE_IND_R0_1: {
			uint8_t addr = (simu.mem.iram + get_rs() * 8)[instr->opcode & 0x1];
			return read_iram(addr);
		}
		case OTYPE_C: {
			return read_bit(SFR_PSW_CY);
		}
		case OTYPE_BIT: {
			return read_bit(op);
		}
		case OTYPE_IND_A_PC: {
			uint16_t addr = read_sfr(SFR_ACC) + simu.pc + 1;
			return simu8051_read(mem_type, addr);
		}
		case OTYPE_IND_A_DPTR: {
			uint16_t dptr = (read_sfr(SFR_DPH) << 8) | read_sfr(SFR_DPL);
			uint16_t addr = read_sfr(SFR_ACC) + dptr;
			return simu8051_read(mem_type, addr);
		}
		case OTYPE_IND_DPTR: {
			uint16_t dptr = (read_sfr(SFR_DPH) << 8) | read_sfr(SFR_DPL);
			return simu8051_read(mem_type, dptr);
		}
		case OTYPE_IND_SP: {
			uint16_t addr = read_sfr(SFR_SP);
			return simu8051_read(mem_type, addr);
		}
		case OTYPE_B: {
			return read_sfr(SFR_B);
		}
		default:
			return 0;
	}
}

static void write_op(instr_t* instr, uint16_t data, int op_num, mem_type_t mem_type) {
	const instr_info_t* info = instr->info;
	op_type_t op_mode = op_num ? info->op1_mode : info->op0_mode;
	uint8_t op = get_op(instr, op_num);
	switch (op_mode) {
		case OTYPE_Rn: {
			int reg = instr->opcode & 0x7;
			(simu.mem.iram + get_rs() * 8)[reg] = (uint8_t)data;
			break;
		}
		case OTYPE_IMM8:
			break;
		case OTYPE_ACC: {
			write_sfr(SFR_ACC, (uint8_t)data);
			break;
		}
		case OTYPE_DIRECT: {
			write_iram(op, (uint8_t)data);
			break;
		}
		case OTYPE_DPTR: {
			write_sfr(SFR_DPH, (uint8_t)(data >> 8));
			write_sfr(SFR_DPL, (uint8_t)data);
			break;
		}
		case OTYPE_IND_R0_1: {
			uint8_t addr = (simu.mem.iram + get_rs() * 8)[instr->opcode & 0x1];
			write_iram(addr, (uint8_t)data);
			break;
		}
		case OTYPE_C: {
			write_bit(SFR_PSW_CY, (uint8_t)data);
			break;
		}
		case OTYPE_BIT: {
			write_bit(op, (uint8_t)data);
			break;
		}
		case OTYPE_IND_DPTR: {
			uint16_t dptr = (read_sfr(SFR_DPH) << 8) | read_sfr(SFR_DPL);
			simu8051_write(mem_type, dptr, (uint8_t)data);
			break;
		}
		case OTYPE_IND_SP: {
			uint16_t addr = read_sfr(SFR_SP);
			simu8051_write(mem_type, addr, (uint8_t)data);
			break;
		}
		case OTYPE_B: {
			write_sfr(SFR_B, (uint8_t)data);
			break;
		}
	}
}

static void do_nop(instr_t* instr) {
	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_sjmp(instr_t* instr) {
	simu.pc += instr->info->bytes;
	simu.pc += (int8_t)instr->op0;
	simu.cycles += instr->info->cycles;
}

static void do_mov(instr_t* instr) {
	const instr_info_t* info = instr->info;

	if ((info->op0_mode == OTYPE_DIRECT) && (info->op1_mode == OTYPE_DIRECT)) {
		uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);
		write_op(instr, src, 1, MEM_TYPE_IRAM);
	}
	else {
		uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
		write_op(instr, src, 0, MEM_TYPE_IRAM);
	}

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_movc(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_CODE);
	write_op(instr, src, 0, MEM_TYPE_CODE);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_movx(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_XRAM);
	write_op(instr, src, 0, MEM_TYPE_XRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_push(instr_t* instr) {
	write_sfr(SFR_SP, read_sfr(SFR_SP) + 1);

	uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
	write_op(instr, src, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_pop(instr_t* instr) {
	uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);
	write_op(instr, src, 1, MEM_TYPE_IRAM);

	write_sfr(SFR_SP, read_sfr(SFR_SP) - 1);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_swap(instr_t* instr) {
	uint16_t acc = read_op(instr, 1, MEM_TYPE_IRAM);
	acc = (acc >> 4) | (acc << 4);
	write_op(instr, acc, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_xch (instr_t* instr) {
	uint16_t v0 = read_op(instr, 0, MEM_TYPE_IRAM);
	uint16_t v1 = read_op(instr, 1, MEM_TYPE_IRAM);

	if (instr->opcode & (1 << 4)) {
		// xchd
		uint16_t temp = v0;
		v0 = (v0 & ~0xF) | (v1 & 0xF);
		v1 = (v1 & ~0xF) | (temp & 0xF);
	} else {
		uint16_t temp = v0;
		v0 = v1;
		v1 = temp;
	}
	write_op(instr, v0, 0, MEM_TYPE_IRAM);
	write_op(instr, v1, 1, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_anl(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
	uint16_t dest = read_op(instr, 0, MEM_TYPE_IRAM);

	write_op(instr, src & dest, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_orl(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
	uint16_t dest = read_op(instr, 0, MEM_TYPE_IRAM);

	write_op(instr, src | dest, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_xrl(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
	uint16_t dest = read_op(instr, 0, MEM_TYPE_IRAM);

	write_op(instr, src ^ dest, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_clr(instr_t* instr) {
	write_op(instr, 0, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_cpl(instr_t* instr) {
	uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);
	write_op(instr, ~src, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_rr(instr_t* instr) {
	uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);

	if (instr->opcode & (1 << 4)) {
		// rrc
		// 
		uint8_t c = read_bit(SFR_PSW_CY);
		uint8_t lsb = src & 0x1;
		write_bit(SFR_PSW_CY, lsb);
		src = (src >> 1) | (c ? 0x80 : 0);
	}
	else {
		src = (src >> 1) | (src << 7);
	}

	write_op(instr, src, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_rl(instr_t* instr) {
	uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);

	if (instr->opcode & (1 << 4)) {
		// rlc
		uint8_t c = read_bit(SFR_PSW_CY);
		uint8_t msb = src >> 7;
		write_bit(SFR_PSW_CY, msb);
		src = c | (src << 1);
	}
	else {
		src = (src >> 7) | (src << 1);
	}

	write_op(instr, src, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_add(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
	uint16_t acc = read_op(instr, 0, MEM_TYPE_IRAM);

	if (instr->opcode & (1 << 4)) {
		src += read_bit(SFR_PSW_CY);
	}
	uint16_t result = src + acc;
	write_op(instr, result, 0, MEM_TYPE_IRAM);

	// 0xF8 + 0xF3 = 0x1??
	write_bit(SFR_PSW_CY, result & 0x100 ? 1 : 0);

	// 0xF8 + 0xF3 => 0x8 + 0x3 ?= 0x1?
	if (((src & 0xF) + (acc & 0xF)) & 0x10) {
		write_bit(SFR_PSW_AC, 1);
	}
	else {
		write_bit(SFR_PSW_AC, 0);
	}

	int bit7_c = result >= 0x100 ? 1 : 0;

	// 0x7??
	int bit6_c = ((acc & 0x7F) + (src & 0x7F)) >= 0x80 ? 1 : 0;

	// 0 0 -- 0 1 -- 1 0 -- 11
	write_bit(SFR_PSW_OV, bit7_c ^ bit6_c);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_subb(instr_t* instr) {
	uint16_t src = read_op(instr, 1, MEM_TYPE_IRAM);
	src += read_bit(SFR_PSW_CY);
	uint16_t acc = read_op(instr, 0, MEM_TYPE_IRAM);
	uint16_t result = acc - src;
	write_op(instr, result, 0, MEM_TYPE_IRAM);

	write_bit(SFR_PSW_CY, acc < src ? 1 : 0);
	if ((acc & 0xF) < (src & 0xF)) {
		write_bit(SFR_PSW_AC, 1);
	}
	else {
		write_bit(SFR_PSW_AC, 0);
	}

	int bit7_c = (acc < src) ? 1 : 0;

	// 0x7??
	int bit6_c = (acc & 0x7F) < (src & 0x7F) ? 1 : 0;

	// 0 0 -- 0 1 -- 1 0 -- 11
	write_bit(SFR_PSW_OV, bit7_c ^ bit6_c);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_inc(instr_t* instr) {
	uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);
	write_op(instr, src + 1, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_dec(instr_t* instr) {
	uint16_t src = read_op(instr, 0, MEM_TYPE_IRAM);
	write_op(instr, src - 1, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}
static void do_mul(instr_t* instr) {
	uint16_t acc = read_op(instr, 0, MEM_TYPE_IRAM);
	uint16_t b = read_op(instr, 1, MEM_TYPE_IRAM);
	uint16_t result = acc * b;

	write_bit(SFR_PSW_OV, result > 255 ? 1 : 0);
	write_bit(SFR_PSW_CY, 0);

	write_op(instr, result & 0xFF, 0, MEM_TYPE_IRAM);
	write_op(instr, result >> 8, 1, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_div(instr_t* instr) {
	uint16_t acc = read_op(instr, 0, MEM_TYPE_IRAM);
	uint16_t b = read_op(instr, 1, MEM_TYPE_IRAM);

	if (b == 0) {
		write_bit(SFR_PSW_OV, 1);
	}
	else {
		// 8 / 3 = 2 ... 2
		write_op(instr, acc / b, 0, MEM_TYPE_IRAM);
		write_op(instr, acc % b, 1, MEM_TYPE_IRAM);
		write_bit(SFR_PSW_OV, 0);
	}
	write_bit(SFR_PSW_CY, 0);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_da(instr_t* instr) {
	uint16_t acc = read_sfr(SFR_ACC);
	uint8_t ac = read_bit(SFR_PSW_AC);
	uint8_t cy = read_bit(SFR_PSW_CY);

	if (((acc & 0xF) > 0x9) || ac) {
		acc += 6;
	}

	if (acc >= 0x100) {
		cy = 1;
	}

	if (((acc & 0xF0) > 0x90) || cy) {
		acc += 0x60;
	}

	if (acc >= 0x100) {
		cy = 1;
	}

	write_sfr(SFR_ACC, (uint8_t)acc);
	write_bit(SFR_PSW_CY, cy);

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_acall(instr_t* instr) {
	uint16_t ret_addr = simu.pc + instr->info->bytes;
	uint8_t sp = read_sfr(SFR_SP) + 1;
	write_iram(sp++, (uint8_t)(ret_addr & 0xFF));
	write_iram(sp, (uint8_t)(ret_addr >> 8));
	write_sfr(SFR_SP, sp);

	simu.pc = (simu.pc & 0xF800) | (instr->op0 | ((instr->opcode & 0xE0) << 3));
	simu.cycles += instr->info->cycles;
}

static void do_lcall(instr_t* instr) {
	uint16_t ret_addr = simu.pc + instr->info->bytes;
	uint8_t sp = read_sfr(SFR_SP) + 1;
	write_iram(sp++, (uint8_t)(ret_addr & 0xFF));
	write_iram(sp, (uint8_t)(ret_addr >> 8));
	write_sfr(SFR_SP, sp);

	simu.pc = (instr->op0 << 8) | instr->op1;
	simu.cycles += instr->info->cycles;
}

static void do_ajmp(instr_t* instr) {
	simu.pc += 2;
	simu.pc = (simu.pc & 0xF800) | (instr->op0 | ((instr->opcode & 0xE0) << 3));
	simu.cycles += instr->info->cycles;
}

static void do_ljmp(instr_t* instr) {
	simu.pc = (instr->op0 << 8) | instr->op1;
	simu.cycles += instr->info->cycles;
}

static void do_jmp_a_dptr(instr_t* instr) {
	uint16_t acc = read_sfr(SFR_ACC);
	uint16_t dptr = (read_sfr(SFR_DPH) << 8) | read_sfr(SFR_DPL);

	simu.pc = acc + dptr;
	simu.cycles += instr->info->cycles;
}

static void do_ret(instr_t* instr) {
	uint16_t ret_addr;

	uint8_t sp = read_sfr(SFR_SP);
	ret_addr = read_iram(sp--) << 8;
	ret_addr |= read_iram(sp--);
	write_sfr(SFR_SP, sp);

	simu.pc = ret_addr;
	simu.cycles += instr->info->cycles;
}

static void do_reti(instr_t* instr) {
	uint16_t ret_addr;

	uint8_t sp = read_sfr(SFR_SP);
	ret_addr = read_iram(sp--) << 8;
	ret_addr |= read_iram(sp--);
	write_sfr(SFR_SP, sp);

	simu.pc = ret_addr;
	simu.cycles += instr->info->cycles;

	simu.in_int = 0;
}

static void do_jz_jnz(instr_t* instr) {
	uint16_t acc = read_sfr(SFR_ACC);
	int8_t rel = (int8_t)instr->op0;

	simu.pc += instr->info->bytes;
	if (instr->opcode & (1 << 4)) {
		// jnz
		simu.pc += acc ? rel : 0;
	}
	else {
		// jz
		simu.pc += !acc ? rel : 0;
	}

	simu.cycles += instr->info->cycles;
}

static void do_djnz(instr_t* instr) {
	simu.pc += instr->info->bytes;

	uint16_t data = read_op(instr, 0, MEM_TYPE_IRAM) - 1;
	write_op(instr, data, 0, MEM_TYPE_IRAM);

	if (data) {
		int8_t rel = (instr->opcode & (1 << 3)) ? (int8_t)instr->op0 : (int8_t)instr->op1;
		simu.pc += rel;
	}

	simu.cycles += instr->info->cycles;
}

static void do_cjne(instr_t* instr) {
	simu.pc += instr->info->bytes;

	uint16_t op0 = read_op(instr, 0, MEM_TYPE_IRAM);
	uint16_t op1;
	if (instr->info->op1_mode == OTYPE_DIRECT) {
		op1 = read_iram(instr->op0);
	}
	else {
		op1 = instr->op0;
	}

	if (op0 != op1) {
		simu.pc += (int8_t)instr->op1;
	}

	if (op0 < op1) {
		write_bit(SFR_PSW_CY, 1);
	}
	else {
		write_bit(SFR_PSW_CY, 0);
	}

	simu.cycles += instr->info->cycles;
}

static void do_anl_bit(instr_t* instr) {
	uint16_t src = read_bit(instr->op0);		// read_op
	uint8_t dest = read_bit(SFR_PSW_CY);

	if (instr->opcode & (1 << 1)) {
		write_bit(SFR_PSW_CY, src & dest);
	}
	else {
		write_bit(SFR_PSW_CY, (~src) & dest);
	}

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_orl_bit(instr_t* instr) {
	uint16_t src = read_bit(instr->op0);		// read_op
	uint8_t dest = read_bit(SFR_PSW_CY);

	if (instr->opcode & (1 << 1)) {
		write_bit(SFR_PSW_CY, src | dest);
	}
	else {
		write_bit(SFR_PSW_CY, (~src) | dest);
	}

	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_setb(instr_t* instr) {
	write_op(instr, 1, 0, MEM_TYPE_IRAM);
	simu.pc += instr->info->bytes;
	simu.cycles += instr->info->cycles;
}

static void do_jc(instr_t* instr) {
	uint8_t cy = read_bit(SFR_PSW_CY);

	simu.pc += instr->info->bytes;
	if (instr->opcode & (1 << 4)) {
		// JNC
		simu.pc += !cy ? (int8_t)instr->op0 : 0;
	}
	else {
		// JC
		simu.pc += cy ? (int8_t)instr->op0 : 0;
	}
	simu.cycles += instr->info->cycles;
}

static void do_jb(instr_t* instr) {
	uint16_t op0 = read_op(instr, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	if (instr->opcode & (1 << 4)) {
		// JNB
		simu.pc += !op0 ? (int8_t)instr->op1 : 0;
	}
	else {
		// JB
		simu.pc += op0 ? (int8_t)instr->op1 : 0;
	}
	simu.cycles += instr->info->cycles;
}

static void do_jbc(instr_t* instr) {
	uint16_t op0 = read_op(instr, 0, MEM_TYPE_IRAM);

	simu.pc += instr->info->bytes;
	if (op0) {
		simu.pc += (int8_t)instr->op1;
		write_bit(instr->op0, 0);

		//write_op(instr, 0, 0, MEM_TYPE_IRAM);
	}
	simu.cycles += instr->info->cycles;
}

static const instr_info_t instr_table[256] = {
	[0x00] = {1, 1, OTYPE_NONE, OTYPE_NONE, do_nop, "NOP"},
	[0x80] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_sjmp, "SJMP re"},
	[0x02] = {3, 2, OTYPE_NONE, OTYPE_NONE, do_ljmp, "LJMP addr16"},
	[0x01] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0x21] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0x41] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0x61] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0x81] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0xA1] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0xC1] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},
	[0xE1] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_ajmp, "AJMP addr11"},

	[0x03] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_rr, "RR A"},
	[0x04] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_inc, "INC A"},
	[0x05] = {2, 1, OTYPE_DIRECT, OTYPE_DIRECT, do_inc, "INC direct"},
	[0x06] = {1, 1, OTYPE_IND_R0_1, OTYPE_IND_R0_1, do_inc, "INC @R0"},
	[0x07] = {1, 1, OTYPE_IND_R0_1, OTYPE_IND_R0_1, do_inc, "INC @R1"},
	[0x08] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R0"},
	[0x09] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R1"},
	[0x0A] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R2"},
	[0x0B] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R3"},
	[0x0C] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R4"},
	[0x0D] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R5"},
	[0x0E] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R6"},
	[0x0F] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_inc, "INC R7"},

	[0x10] = {3, 2, OTYPE_BIT, OTYPE_NONE, do_jbc, "JBC bit,rel"},
	[0x11] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0x12] = {3, 2, OTYPE_NONE, OTYPE_NONE, do_lcall, "LCALL addr16"},
	[0x31] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0x51] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0x71] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0x91] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0xB1] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0xD1] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},
	[0xF1] = {2, 2, OTYPE_NONE, OTYPE_NONE, do_acall, " ACALL addr11"},

	[0x13] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_rr, "RRC A"},
	[0x14] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_dec, "DEC A"},
	[0x15] = {2, 1, OTYPE_DIRECT, OTYPE_DIRECT, do_dec, "DEC direct"},
	[0x16] = {1, 1, OTYPE_IND_R0_1, OTYPE_IND_R0_1, do_dec, "DEC @R0"},
	[0x17] = {1, 1, OTYPE_IND_R0_1, OTYPE_IND_R0_1, do_dec, "DEC @R1"},
	[0x18] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R0"},
	[0x19] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R1"},
	[0x1A] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R2"},
	[0x1B] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R3"},
	[0x1C] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R4"},
	[0x1D] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R5"},
	[0x1E] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R6"},
	[0x1F] = {1, 1, OTYPE_Rn, OTYPE_Rn, do_dec, "DEC R7"},

	[0x20] = {3, 2, OTYPE_BIT, OTYPE_NONE, do_jb, "JB bit,rel"},
	[0x22] = {1, 2, OTYPE_NONE, OTYPE_NONE, do_ret, "RET"},
	[0x23] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_rl, "RL A"},
	[0x24] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_add, "ADD A,#data"},
	[0x25] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_add, "ADD A,direct"},
	[0x26] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_add, "ADD A,@R0"},
	[0x27] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_add, "ADD A,@R1"},
	[0x28] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R0"},
	[0x29] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R1"},
	[0x2A] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R2"},
	[0x2B] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R3"},
	[0x2C] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R4"},
	[0x2D] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R5"},
	[0x2E] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R6"},
	[0x2F] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADD A,R7"},

	[0x30] = {3, 2, OTYPE_BIT, OTYPE_NONE, do_jb, "JNB bit,rel"},
	[0x32] = {1, 2, OTYPE_NONE, OTYPE_NONE, do_reti, "RETI"},
	[0x33] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_rl, "RLC A"},
	[0x34] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_add, "ADDC A,#data"},
	[0x35] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_add, "ADDC A,direct"},
	[0x36] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_add, "ADDC A,@R0"},
	[0x37] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_add, "ADDC A,@R1"},
	[0x38] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R0"},
	[0x39] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R1"},
	[0x3A] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R2"},
	[0x3B] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R3"},
	[0x3C] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R4"},
	[0x3D] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R5"},
	[0x3E] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R6"},
	[0x3F] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_add, "ADDC A,R7"},

	[0x40] = {2, 2, OTYPE_C, OTYPE_NONE, do_jc, "JC rel"},
	[0x42] = {2, 1, OTYPE_DIRECT, OTYPE_ACC, do_orl, "ORL direct,A"},
	[0x43] = {3, 2, OTYPE_DIRECT, OTYPE_IMM8, do_orl, "ORL direct,#data"},
	[0x44] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_orl, "ORL A,#data"},
	[0x45] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_orl, "ORL A,direct"},
	[0x46] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_orl, "ORL A,@R0"},
	[0x47] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_orl, "ORL A,@R1"},
	[0x48] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R0"},
	[0x49] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R1"},
	[0x4A] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R2"},
	[0x4B] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R3"},
	[0x4C] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R4"},
	[0x4D] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R5"},
	[0x4E] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R6"},
	[0x4F] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_orl, "ORL A,R7"},

	[0x50] = {2, 2, OTYPE_C, OTYPE_NONE, do_jc, "JNC rel"},
	[0x52] = {2, 1, OTYPE_DIRECT, OTYPE_ACC, do_anl, "ANL direct,A"},
	[0x53] = {3, 2, OTYPE_DIRECT, OTYPE_IMM8, do_anl, "ANL direct,#data"},
	[0x54] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_anl, "ANL A,#data"},
	[0x55] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_anl, "ANL A,direct"},
	[0x56] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_anl, "ANL A,@R0"},
	[0x57] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_anl, "ANL A,@R1"},
	[0x58] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R0"},
	[0x59] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R1"},
	[0x5A] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R2"},
	[0x5B] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R3"},
	[0x5C] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R4"},
	[0x5D] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R5"},
	[0x5E] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R6"},
	[0x5F] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_anl, "ANL A,R7"},

	[0x60] = {2, 2, OTYPE_IMM8, OTYPE_NONE, do_jz_jnz, "JZ rel"},
	[0x62] = {2, 1, OTYPE_DIRECT, OTYPE_ACC, do_xrl, "XRL direct,A"},
	[0x63] = {3, 2, OTYPE_DIRECT, OTYPE_IMM8, do_xrl, "XRL direct,#data"},
	[0x64] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_xrl, "XRL A,#data"},
	[0x65] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_xrl, "XRL A,direct"},
	[0x66] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_xrl, "XRL A,@R0"},
	[0x67] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_xrl, "XRL A,@R1"},
	[0x68] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R0"},
	[0x69] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R1"},
	[0x6A] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R2"},
	[0x6B] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R3"},
	[0x6C] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R4"},
	[0x6D] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R5"},
	[0x6E] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R6"},
	[0x6F] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xrl, "XRL A,R7"},

	[0x70] = { 2, 2, OTYPE_NONE, OTYPE_NONE, do_jz_jnz, "JNZ rel" },
	[0x72] = {2, 2, OTYPE_C, OTYPE_BIT, do_orl_bit, "ORL C, bit"},
	[0x73] = {1, 2, OTYPE_NONE, OTYPE_NONE, do_jmp_a_dptr, "JMP @A+DPTR"},
	[0x74] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_mov, "MOV A, #imm8"},
	[0x75] = {3, 2, OTYPE_DIRECT, OTYPE_IMM8, do_mov, "MOV direct,#data"},

	[0x76] = {2, 1, OTYPE_IND_R0_1, OTYPE_IMM8, do_mov, "MOV @R0,#data"},  // @R0, @R1
	[0x77] = {2, 1, OTYPE_IND_R0_1, OTYPE_IMM8, do_mov, "MOV @R1,#data"},  // @R0, @R1
	[0x78] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R0,#imm8"},
	[0x79] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R1,#imm8"},
	[0x7A] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R2,#imm8"},
	[0x7B] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R3,#imm8"},
	[0x7C] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R4,#imm8"},
	[0x7D] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R5,#imm8"},
	[0x7E] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R6,#imm8"},
	[0x7F] = {2, 1, OTYPE_Rn, OTYPE_IMM8, do_mov, "MOV R7,#imm8"},

	[0x82] = {2, 2, OTYPE_C, OTYPE_BIT, do_anl_bit, "ANL C,bit"},
	[0x83] = {1, 2, OTYPE_ACC, OTYPE_IND_A_PC, do_movc, "MOVC A,@A+PC"},
	[0x84] = {1, 4, OTYPE_ACC, OTYPE_B, do_div, "DIV AB"},
	[0x85] = {3, 2, OTYPE_DIRECT, OTYPE_DIRECT, do_mov, "MOV direct, direct"},
	[0x86] = {2, 2, OTYPE_DIRECT, OTYPE_IND_R0_1, do_mov, "MOV direct,@R0"},
	[0x87] = {2, 2, OTYPE_DIRECT, OTYPE_IND_R0_1, do_mov, "MOV direct,@R1"},
	[0x88] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R0"},
	[0x89] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R1"},
	[0x8A] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R2"},
	[0x8B] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R3"},
	[0x8C] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R4"},
	[0x8D] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R5"},
	[0x8E] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R6"},
	[0x8F] = {2, 2, OTYPE_DIRECT, OTYPE_Rn, do_mov, "MOV direct,R7"},

	[0x90] = {3, 2, OTYPE_DPTR, OTYPE_IMM16, do_mov, "MOV DPTR,#data16"},
	[0x92] = {2, 2, OTYPE_BIT, OTYPE_C, do_mov, "MOV bit,C"},
	[0x93] = {1, 2, OTYPE_ACC, OTYPE_IND_A_DPTR, do_movc, "MOVC A,@A+DPTR"},
	[0x94] = {2, 1, OTYPE_ACC, OTYPE_IMM8, do_subb, "SUBB A,#data"},
	[0x95] = { 2, 1, OTYPE_ACC, OTYPE_DIRECT, do_subb, "SUBB A,direct" },
	[0x96] = { 1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_subb, "SUBB A,@R0" },
	[0x97] = { 1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_subb, "SUBB A,@R1" },
	[0x98] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R0" },
	[0x99] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R1" },
	[0x9A] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R2" },
	[0x9B] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R3" },
	[0x9C] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R4" },
	[0x9D] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R5" },
	[0x9E] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R6" },
	[0x9F] = { 1, 1, OTYPE_ACC, OTYPE_Rn, do_subb, "SUBB A,R7" },

	[0xA0] = { 2, 2, OTYPE_C, OTYPE_BIT, do_orl_bit, "ORL C, /bit" },
	[0xA2] = {2, 1, OTYPE_C, OTYPE_BIT, do_mov, "MOV C,bit"},
	[0xA3] = {1, 2, OTYPE_DPTR, OTYPE_DPTR, do_inc, "INC DPTR"},
	[0xA4] = {1, 4, OTYPE_ACC, OTYPE_B, do_mul, "MUL AB"},
	[0xA6] = {2, 2, OTYPE_IND_R0_1, OTYPE_DIRECT, do_mov, "MOV @R0,direct"},  // @R0, @R1
	[0xA7] = {2, 2, OTYPE_IND_R0_1, OTYPE_DIRECT, do_mov, "MOV @R1,direct"},  // @R0, @R1
	[0xA8] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R0,direct"},
	[0xA9] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R1,direct"},
	[0xAA] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R2,direct"},
	[0xAB] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R3,direct"},
	[0xAC] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R4,direct"},
	[0xAD] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R5,direct"},
	[0xAE] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R6,direct"},
	[0xAF] = {2, 2, OTYPE_Rn, OTYPE_DIRECT, do_mov, "MOV R7,direct"},

	[0xB0] = {2, 2, OTYPE_C, OTYPE_BIT, do_anl_bit, "ANL C, /bit"},
	[0xB2] = {2, 1, OTYPE_BIT, OTYPE_BIT, do_cpl, "CPL bit"},
	[0xB3] = {1, 1, OTYPE_C, OTYPE_C, do_cpl, "CPL C"},
	[0xB4] = {3, 2, OTYPE_ACC, OTYPE_IMM8, do_cjne, "CJNE A,#data,rel"},
	[0xB5] = {3, 2, OTYPE_ACC, OTYPE_DIRECT, do_cjne, "CJNE A,direct,rel"},
	[0xB6] = {3, 2, OTYPE_IND_R0_1, OTYPE_IMM8, do_cjne, "CJNE @R0,data,rel"},
	[0xB7] = { 3, 2, OTYPE_IND_R0_1, OTYPE_IMM8, do_cjne, "CJNE @R1,data,rel" },
	[0xB8] = {3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R0,#data,rel"},
	[0xB9] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R1,#data,rel" },
	[0xBA] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R2,#data,rel" },
	[0xBB] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R3,#data,rel" },
	[0xBC] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R4,#data,rel" },
	[0xBD] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R5,#data,rel" },
	[0xBE] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R6,#data,rel" },
	[0xBF] = { 3, 2, OTYPE_Rn, OTYPE_IMM8, do_cjne, "CJNE R7,#data,rel" },

	[0xC0] = {2, 2, OTYPE_IND_SP, OTYPE_DIRECT, do_push, "PUSH direct"},
	[0xC2] = {2, 1, OTYPE_BIT, OTYPE_BIT, do_clr, "CLR bit" },
	[0xC3] = {1, 1, OTYPE_C, OTYPE_C, do_clr, "CLR C"},
	[0xC4] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_swap, "SWAP A"},
	[0xC5] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_xch, "XCH A,direct"},
	[0xC6] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_xch, "XCH A,@R0"},
	[0xC7] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_xch, "XCH A,@R1"},
	[0xC8] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R0"},
	[0xC9] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R1"},
	[0xCA] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R2"},
	[0xCB] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R3"},
	[0xCC] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R4"},
	[0xCD] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R5"},
	[0xCE] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R6"},
	[0xCF] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_xch, "XCH A,R7"},

	[0xD0] = {2, 2, OTYPE_IND_SP, OTYPE_DIRECT, do_pop, "POP direct"},
	[0xD2] = {2, 1, OTYPE_BIT, OTYPE_BIT, do_setb, "SETB bit" },
	[0xD3] = {1, 1, OTYPE_C, OTYPE_C, do_setb, "SETB C" },
	[0xD4] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_da, "DA A"},
	[0xD5] = {3, 2, OTYPE_DIRECT, OTYPE_NONE, do_djnz, "DJNZ direct,rel"},
	[0xD6] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_xch, "XCHD A,@R0"},
	[0xD7] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_xch, "XCHD A,@R1"},
	[0xD8] = {2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R0,rel"},
	[0xD9] = {2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R1,rel"},
	[0xDA] = { 2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R2,rel" },
	[0xDB] = { 2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R3,rel" },
	[0xDC] = { 2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R4,rel" },
	[0xDD] = { 2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R5,rel" },
	[0xDE] = { 2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R6,rel" },
	[0xDF] = { 2, 2, OTYPE_Rn, OTYPE_NONE, do_djnz, "DJNZ R7,rel" },


	[0xE0] = {1, 2, OTYPE_ACC, OTYPE_IND_DPTR, do_movx, "MOVX A,@DPTR"},
	[0xE2] = {1, 2, OTYPE_ACC, OTYPE_IND_R0_1, do_movx, "MOVX A,@R0"},
	[0xE3] = {1, 2, OTYPE_ACC, OTYPE_IND_R0_1, do_movx, "MOVX A,@R1"},
	[0xE4] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_clr, "CLR A"},
	[0xE5] = {2, 1, OTYPE_ACC, OTYPE_DIRECT, do_mov, "MOV A,direct"},
	[0xE6] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_mov, "MOV A,@R0"},  // @R0, @R1
	[0xE7] = {1, 1, OTYPE_ACC, OTYPE_IND_R0_1, do_mov, "MOV A,@R1"},  // @R0, @R1
	[0xE8] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R0"},
	[0xE9] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R1"},
	[0xEA] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R2"},
	[0xEB] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R3"},
	[0xEC] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R4"},
	[0xED] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R5"},
	[0xEE] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R6"},
	[0xEF] = {1, 1, OTYPE_ACC, OTYPE_Rn, do_mov, "MOV A,R7"},

	[0xF0] = {1, 2, OTYPE_IND_DPTR, OTYPE_ACC, do_movx, "MOVX @DPTR, A"},
	[0xF2] = {1, 2, OTYPE_IND_R0_1, OTYPE_ACC, do_movx, "MOVX @R0, A"},
	[0xF3] = {1, 2, OTYPE_IND_R0_1, OTYPE_ACC, do_movx, "MOVX @R1, A"},
	[0xF4] = {1, 1, OTYPE_ACC, OTYPE_ACC, do_cpl, "CPL A"},
	[0xF5] = {2, 1, OTYPE_DIRECT, OTYPE_ACC, do_mov, "MOV direct, A"},
	[0xF6] = {1, 1, OTYPE_IND_R0_1, OTYPE_ACC, do_mov, "MOV @R0, A"},  // @R0, @R1
	[0xF7] = {1, 1, OTYPE_IND_R0_1, OTYPE_ACC, do_mov, "MOV @R1, A"},  // @R0, @R1
	[0xF8] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R0, A"},
	[0xF9] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R1, A"},
	[0xFA] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R2, A"},
	[0xFB] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R3, A"},
	[0xFC] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R4, A"},
	[0xFD] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R5, A"},
	[0xFE] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R6, A"},
	[0xFF] = {1, 1, OTYPE_Rn, OTYPE_ACC, do_mov, "MOV R7, A"},
};

void simu8051_reset(void) {
	simu.pc = 0;
	simu.cycles = 0;
	simu.in_int = 0;

	memset(simu.mem.xram, 0, MEM_XRAM_SIZE);
	memset(simu.mem.iram, 0, MEM_IRAM_SIZE);
	memset(simu.mem.sfr, 0, MEM_SFR_SIZE);
	write_sfr(SFR_SP, 0x7);
}


void simu8051_init(void) {
	simu.mem.code = (uint8_t*)0;
	simu8051_reset();
}

void simu8051_load(uint8_t* code) {
	simu.mem.code = code;
}

uint16_t simu8051_pc(void) {
	return simu.pc;
}

uint32_t simu8051_cycle(void) {
	return simu.cycles;
}

void simu8051_fetch_instr(instr_t* instr) {
	instr->opcode = simu.mem.code[simu.pc];
	instr->info = instr_table + instr->opcode;

	instr->op0 = simu.mem.code[simu.pc + 1];
	instr->op1 = simu.mem.code[simu.pc + 2];
}

static void update_psw_p(void) {
	uint8_t v = read_sfr(SFR_ACC);

	int count = 0;
	while (v) {
		if (v & 0x1) {
			count++;
		}

		v >>= 1;
	}
	write_bit(SFR_PSW_P, count & 0x1);
}

static void update_timer(int cycles) {
	uint8_t tmod = read_sfr(SFR_TMOD);
	uint8_t tr0 = read_bit(SFR_TCON_TR0);

	if (tr0) {
		switch (tmod & 0x3) {
			case 0x00:
			case 0x2:
			case 0x3:
				break;
			case 0x1: {
				int count = (read_sfr(SFR_TH0) << 8) | read_sfr(SFR_TL0);
				count += cycles;
				if (count >= 0x10000) {
					write_bit(SFR_TCON_TF0, 1);
					count = 0;
				}
				write_sfr(SFR_TH0, count >> 8);
				write_sfr(SFR_TL0, count & 0xFF);
				break;
			}
		}
	}
}

static void enter_interrupt(uint16_t entry) {
	uint8_t sp = read_sfr(SFR_SP);

	simu.in_int = 1;

	write_iram(++sp, simu.pc & 0xFF);
	write_iram(++sp, simu.pc >> 8);
	write_sfr(SFR_SP, sp);

	simu.pc = entry;
}

static void process_interrupt(void) {
	uint8_t ie = read_sfr(SFR_IE);
	if ((ie & 0x80) == 0) {
		return;
	}

	if (simu.in_int) {
		return;
	}

	if (ie & (1 << 1)) {
		uint8_t tf0 = read_bit(SFR_TCON_TF0);
		if (tf0) {
			enter_interrupt(INT_TIMER0_ENTRY);

			write_bit(SFR_TCON_TF0, 0);
		}
	}
}

void simu8051_exec(instr_t* instr) {
	instr->info->exec(instr);
	update_psw_p();

	if (simu8051_uart_rx_rdy()) {
		write_bit(SFR_SCON_RI, 1);
	}

	update_timer(instr->info->cycles);
	process_interrupt();
}

void simu8051_run(uint16_t stop_addr, uint32_t stop_cyle) {
	do {
		instr_t instr;

		simu8051_fetch_instr(&instr);
		simu8051_exec(&instr);
		if (simu.pc == stop_addr) {
			return;
		}

		if (stop_cyle && (simu.cycles >= stop_cyle)) {
			return;
		}

	} while (1);
}
